package com.ceair.authorization.device;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.lang.Nullable;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;

/**
 * @author wangbaohai
 * @ClassName DeviceClientAuthenticationConverter
 * @description: 获取请求中参数转化为DeviceClientAuthenticationToken
 * @date 2024年11月15日
 * @version: 1.0.0
 */
public final class DeviceClientAuthenticationConverter implements AuthenticationConverter {

    private final RequestMatcher deviceAuthorizationRequestMatcher;
    private final RequestMatcher deviceAccessTokenRequestMatcher;

    /**
     * 构造函数用于初始化设备授权端点的请求匹配器
     *
     * @param deviceAuthorizationEndpointUri 设备授权的端点URI，用于处理设备授权请求
     */
    public DeviceClientAuthenticationConverter(String deviceAuthorizationEndpointUri) {
        // 创建一个请求匹配器，用于匹配包含客户端ID参数的请求
        RequestMatcher clientIdParameterMatcher = request ->
                request.getParameter(OAuth2ParameterNames.CLIENT_ID) != null;

        // 初始化设备授权请求的匹配器，仅匹配POST请求且请求路径符合设备授权端点URI，
        // 同时请求中包含客户端ID参数
        this.deviceAuthorizationRequestMatcher = new AndRequestMatcher(
                new AntPathRequestMatcher(
                        deviceAuthorizationEndpointUri, HttpMethod.POST.name()),
                clientIdParameterMatcher);

        // 初始化设备访问令牌请求的匹配器，匹配包含设备代码和客户端ID参数，
        // 且授权类型为设备代码的请求
        this.deviceAccessTokenRequestMatcher = request ->
                AuthorizationGrantType.DEVICE_CODE.getValue().equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) &&
                        request.getParameter(OAuth2ParameterNames.DEVICE_CODE) != null &&
                        request.getParameter(OAuth2ParameterNames.CLIENT_ID) != null;
    }

    /**
     * 尝试从HTTP请求中转换出设备认证信息
     *
     * 本方法主要用于处理设备认证流程中的客户端认证请求它通过检查请求是否符合设备授权请求或设备访问令牌请求的模式来决定是否进行处理
     * 如果请求不符合预期模式，则返回null，表示无法从当前请求中转换出认证信息
     *
     * @param request 当前的HTTP请求对象，包含所有请求参数和头信息
     * @return 如果请求符合设备认证流程的预期模式，则返回设备客户端认证令牌；否则返回null
     * @throws OAuth2AuthenticationException 如果请求中的client_id参数不符合要求，则抛出此异常，表示认证请求无效
     */
    @Nullable
    @Override
    public Authentication convert(HttpServletRequest request) {
        // 检查请求是否符合设备授权请求或设备访问令牌请求的匹配模式
        if (!this.deviceAuthorizationRequestMatcher.matches(request) &&
                !this.deviceAccessTokenRequestMatcher.matches(request)) {
            return null;
        }

        // 从请求中获取client_id参数，这是设备认证流程中标识客户端的唯一ID
        // client_id (REQUIRED)
        String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);
        // 验证client_id参数是否存在且唯一，如果不符合要求，则抛出认证异常
        if (!StringUtils.hasText(clientId) ||
                request.getParameterValues(OAuth2ParameterNames.CLIENT_ID).length != 1) {
            throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
        }

        // 创建并返回设备客户端认证令牌，此时仅包含client_id，认证方法为NONE，表示尚未完成客户端认证
        return new DeviceClientAuthenticationToken(clientId, ClientAuthenticationMethod.NONE, null, null);
    }

}
